/*******************************************************************************
 * The MOSES project, model based software engineering of networked embedded systems.
 * 
 * Copyright (C) 2010-2013, University of Applied Sciences Wildau,
 * Telematics Research Group, http://www.tm.th-wildau.de/research-group
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses>.
 * 
 * Contributors:
 *     University of Applied Sciences Wildau - initial API and implementation
 * 
 * If you are interested in a commercial license please contact us via 
 * e-mail: ralf.vandenhouten@th-wildau.de.
 ******************************************************************************/
package de.thwildau.tm.moses.thrift.ui.builder;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.inject.Inject;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.xtext.builder.IXtextBuilderParticipant;
import org.eclipse.xtext.util.Strings;

import de.thwildau.tm.moses.common.builder.IBuildContextVisitor;
import de.thwildau.tm.moses.common.builder.IProcessExecutor;
import de.thwildau.tm.moses.common.builder.IProcessExecutor.ResultType;
import de.thwildau.tm.moses.thrift.ui.Activator;
import de.thwildau.tm.moses.thrift.ui.preferences.PreferencesConstants;

/**
 * A Builder Participant, that compiles the given thrift-file with the Thrift Compiler. The (possible) error messages of the Thrift
 * Compilers are added as Markers to the given Thrift-file.
 * 
 * @author Thomas Kistel
 * @version $Revision: 756 $
 */
public class ThriftBuildParticipant implements IXtextBuilderParticipant {
    
    private static Pattern ERROR_LINE_PATTERN = Pattern.compile("\\[(.*)/(.*):(\\d+)\\]\\s(.*)", Pattern.DOTALL);

    private static final String SRC_GEN_FOLDER_NAME = "src-gen";
    
    @Inject
    private IBuildContextVisitor contextVisitor;
    
    @Inject
    private IProcessExecutor processExecutor;
    
    public ThriftBuildParticipant() {
    }

    /*
     * (non-Javadoc)
     * @see IXtextBuilderParticipant#build(org.eclipse.xtext.builder.IXtextBuilderParticipant.IBuildContext, org.eclipse.core.runtime.IProgressMonitor)
     */
    @Override
    public void build(IBuildContext context, IProgressMonitor monitor) throws CoreException {
        if(monitor == null)
            monitor = new NullProgressMonitor();
        
        if(shouldBuild(context)) {
            IProject project = context.getBuiltProject();
            List<IFile> thriftFiles = contextVisitor.getAffectedFiles(context, "thrift");
            for (IFile thriftFile : thriftFiles) {
                IProject projectOfThriftFile = thriftFile.getProject();
                if(!project.equals(projectOfThriftFile)) {
                    Activator.logWarning("The Project of the BuildContext " + project.getName() + " is not equal with the Project " + projectOfThriftFile.getName() + " of the given thrift-file " + thriftFile.getName());
                }
                IJavaProject javaProject = prepareProject(projectOfThriftFile, monitor);
                if(javaProject.isOnClasspath(thriftFile)) {
                    String message = "Compiling Thrift file " + thriftFile.getName();
                    monitor.setTaskName(message);
                    String targetDir = project.getFolder(SRC_GEN_FOLDER_NAME).getLocation().toOSString();
                    execCommand(targetDir, thriftFile, monitor);
                } else {
                    Activator.debugInfo("The file " + thriftFile.getName() + " is not in the Java classpath or not a Thrift file.");
                }
            }  
            project.refreshLocal(IResource.DEPTH_INFINITE, monitor);
        }
    }
    
    private IJavaProject prepareProject(IProject project, IProgressMonitor monitor) throws CoreException {
        IFolder srcGenFolder = project.getFolder(SRC_GEN_FOLDER_NAME);
        project.refreshLocal(IResource.DEPTH_ZERO, monitor);
        IJavaProject javaProject = JavaCore.create(project);
        if (!srcGenFolder.exists()) {
            monitor.setTaskName("Creating Source folder for generated Thrift files.");
            createSourceFolder(javaProject, srcGenFolder, monitor);
        }
        return javaProject;
        
    }
    
    private void createSourceFolder(IJavaProject javaProject, IFolder sourceFolder, IProgressMonitor monitor) throws CoreException {
        sourceFolder.create(true, true, monitor);
        // add the folder as a SourceFolder of the JavaProject
        IPackageFragmentRoot root = javaProject.getPackageFragmentRoot(sourceFolder);
        IClasspathEntry[] oldEntries = javaProject.getRawClasspath();
        IClasspathEntry[] newEntries = new IClasspathEntry[oldEntries.length + 1];
        System.arraycopy(oldEntries, 0, newEntries, 0, oldEntries.length);
        newEntries[oldEntries.length] = JavaCore.newSourceEntry(root.getPath());
        javaProject.setRawClasspath(newEntries, null);
    }

    private void execCommand(String targetDir, final IFile sourceFile, IProgressMonitor monitor) throws CoreException {
        String compilerPath = getCompilerPath();
        checkExist(compilerPath, "Thrift Compiler");
        checkExist(targetDir, "Output Directory");

        String sourceFilePath = sourceFile.getLocation().toOSString();
        String command = buildCommand(compilerPath, targetDir, sourceFilePath);
        
        processExecutor.execCommand(command, new IProcessExecutor.ResultHandler.Stub() {
            
            @Override
            public void onFinished(List<String> results) {
                try {
                    ThriftMarkerFactory markerFactory = new ThriftMarkerFactory(sourceFile);
                    results = flatResults(results);
                    for(String errorMsg : results) {
                        Matcher matcher = ERROR_LINE_PATTERN.matcher(errorMsg);
                        createMarker(markerFactory, matcher);
                    }
                } catch (CoreException e) {
                    StatusManager.getManager().handle(e, Activator.getBundleId());
                }
            }
            
            /*
             * Instead of this, we could better use Pattern.MULTILINE !?
             */
            private List<String> flatResults(List<String> results) {
//                String currentLine = null;
                String lastLine = null;
                List<String> flatList = new ArrayList<String>();
                for(String errorMsg : results) {
                    Matcher matcher = ERROR_LINE_PATTERN.matcher(errorMsg);
                    if (matcher.matches()) {
                        flatList.add(errorMsg);
                        lastLine = errorMsg;
                    } else {
                        if(lastLine != null) {
                            flatList.remove(lastLine);
                            flatList.add(lastLine + " " + errorMsg);
                        }
                    }
                }
                return flatList;
            }
            
            private void createMarker(ThriftMarkerFactory markerFactory, Matcher matcher) throws CoreException {
                if(matcher.matches()) {
                    String fileName = matcher.group(2);
                    String lineNumber = matcher.group(3);
                    String message = getTrimmedMessage(matcher);
                    markerFactory.createMarkerIfNecessary(fileName, message, Integer.parseInt(lineNumber));
                }
            }
            
            private String getTrimmedMessage(Matcher matcher) {
                int count = matcher.groupCount();
                String msg = matcher.group(4) + (count == 5 ? matcher.group(5) : "");
                return msg.replaceAll("\r|\n|\r\n", "");
            }
        }, ResultType.ERROR_STREAM);
    }
    
    private void checkExist(String absolutePath, String label) throws CoreException {
        boolean exist = new File(absolutePath).exists();
        if(!exist) {
            String message = "Could not compile Thrift file, because path ('"+ absolutePath+"') to " + label + " does not exist";
            throw new CoreException(new Status(IStatus.ERROR, Activator.getBundleId(), message));
        }
    }

    private String buildCommand(String compilerPath, String targetDir, String sourceFile) {
        StringBuilder buf = new StringBuilder();
        buf.append(compilerPath);
        buf.append(" -out ");
        buf.append(targetDir);
        buf.append(" --gen java ");
        buf.append(sourceFile);
        return buf.toString();
    }
    
    private String getCompilerPath() {
        IPreferenceStore prefs = Activator.getInstance().getPreferenceStore();
        return prefs.getString(PreferencesConstants.PREF_KEY_COMPILER_PATH);
    }
    
    private boolean shouldBuild(IBuildContext context) {
        if(Strings.isEmpty(getCompilerPath()))
            return false;
        
        switch (context.getBuildType()) {
            case INCREMENTAL:
                IPreferenceStore prefs = Activator.getInstance().getPreferenceStore();
                return prefs.getBoolean(PreferencesConstants.PREF_KEY_AUTO_COMPILE);
            default:
                return true;
        }
    }
}
